#pragma once
#ifndef NET_H
#define NET_H

#include "types.h"

//
// packet buffer management
//

#define MBUF_SIZE 2048
#define MBUF_DEFAULT_HEADROOM 128

struct mbuf {
    struct mbuf *next; // the next mbuf in the chain
    char *head; // the current start position of the buffer
    unsigned int len; // the length of the buffer
    char buf[MBUF_SIZE]; // the backing store
};

char *mbufpull(struct mbuf *m, unsigned int len);
char *mbufpush(struct mbuf *m, unsigned int len);
char *mbufput(struct mbuf *m, unsigned int len);
char *mbuftrim(struct mbuf *m, unsigned int len);

// The above functions manipulate the size and position of the buffer:
//            <- push            <- trim
//             -> pull            -> put
// [-headroom-][------buffer------][-tailroom-]
// |----------------MBUF_SIZE-----------------|
//
// These marcos automatically typecast and determine the size of header structs.
// In most situations you should use these instead of the raw ops above.
#define mbufpullhdr(mbuf, hdr) (typeof(hdr) *)mbufpull(mbuf, sizeof(hdr))
#define mbufpushhdr(mbuf, hdr) (typeof(hdr) *)mbufpush(mbuf, sizeof(hdr))
#define mbufputhdr(mbuf, hdr) (typeof(hdr) *)mbufput(mbuf, sizeof(hdr))
#define mbuftrimhdr(mbuf, hdr) (typeof(hdr) *)mbuftrim(mbuf, sizeof(hdr))

struct mbuf *mbufalloc(unsigned int headroom);
void mbuffree(struct mbuf *m);

struct mbufq {
    struct mbuf *head; // the first element in the queue
    struct mbuf *tail; // the last element in the queue
};

void mbufq_pushtail(struct mbufq *q, struct mbuf *m);
struct mbuf *mbufq_pophead(struct mbufq *q);
int mbufq_empty(struct mbufq *q);
void mbufq_init(struct mbufq *q);

//
// endianness support
//

static inline uint16 bswaps(uint16 val)
{
    return (((val & 0x00ffU) << 8) | ((val & 0xff00U) >> 8));
}

static inline uint32 bswapl(uint32 val)
{
    return (((val & 0x000000ffUL) << 24) | ((val & 0x0000ff00UL) << 8) |
            ((val & 0x00ff0000UL) >> 8) | ((val & 0xff000000UL) >> 24));
}

static inline u64 bswapp(uint64 val)
{
    u64 ret = 0;
    ret |= ((val & 0xff) << 56);
    ret |= ((val & 0xff00) << 40);
    ret |= ((val & 0xff0000) << 24);
    ret |= ((val & 0xff000000) << 8);
    ret |= ((val & 0xff00000000) >> 8);
    ret |= ((val & 0xff0000000000) >> 24);
    ret |= ((val & 0xff000000000000) >> 40);
    ret |= ((val & 0xff00000000000000) >> 56);
    return ret;
}

// Use these macros to convert network bytes to the native byte order.
// Note that Risc-V uses little endian while network order is big endian.
#define ntohs bswaps
#define ntohl bswapl
#define htons bswaps
#define htonl bswapl

//
// useful networking headers
//

#define ETHADDR_LEN 6

// an Ethernet packet header (start of the packet).
struct eth {
    uint8 dhost[ETHADDR_LEN];
    uint8 shost[ETHADDR_LEN];
    uint16 type;
} __attribute__((packed));

#define ETHTYPE_IP 0x0800 // Internet protocol
#define ETHTYPE_ARP 0x0806 // Address resolution protocol

// an IP packet header (comes after an Ethernet header).
struct ip {
    uint8 ip_vhl; // version << 4 | header length >> 2
    uint8 ip_tos; // type of service
    uint16 ip_len; // total length
    uint16 ip_id; // identification
    uint16 ip_off; // fragment offset field
    uint8 ip_ttl; // time to live
    uint8 ip_p; // protocol
    uint16 ip_sum; // checksum
    uint32 ip_src, ip_dst;
};

#define IPPROTO_ICMP 1 // Control message protocol
#define IPPROTO_TCP 6 // Transmission control protocol
#define IPPROTO_UDP 17 // User datagram protocol

#define MAKE_IP_ADDR(a, b, c, d) \
    (((uint32)a << 24) | ((uint32)b << 16) | ((uint32)c << 8) | (uint32)d)

// a UDP packet header (comes after an IP header).
struct udp {
    uint16 sport; // source port
    uint16 dport; // destination port
    uint16 ulen; // length, including udp header, not including IP header
    uint16 sum; // checksum
};

// an ARP packet (comes after an Ethernet header).
struct arp {
    uint16 hrd; // format of hardware address
    uint16 pro; // format of protocol address
    uint8 hln; // length of hardware address
    uint8 pln; // length of protocol address
    uint16 op; // operation

    char sha[ETHADDR_LEN]; // sender hardware address
    uint32 sip; // sender IP address
    char tha[ETHADDR_LEN]; // target hardware address
    uint32 tip; // target IP address
} __attribute__((packed));

#define ARP_HRD_ETHER 1 // Ethernet

enum {
    ARP_OP_REQUEST = 1, // requests hw addr given protocol addr
    ARP_OP_REPLY = 2, // replies a hw addr given protocol addr
};

// an DNS packet (comes after an UDP header).
struct dns {
    uint16 id; // request ID

    uint8 rd : 1; // recursion desired
    uint8 tc : 1; // truncated
    uint8 aa : 1; // authoritive
    uint8 opcode : 4;
    uint8 qr : 1; // query/response
    uint8 rcode : 4; // response code
    uint8 cd : 1; // checking disabled
    uint8 ad : 1; // authenticated data
    uint8 z : 1;
    uint8 ra : 1; // recursion available

    uint16 qdcount; // number of question entries
    uint16 ancount; // number of resource records in answer section
    uint16 nscount; // number of NS resource records in authority section
    uint16 arcount; // number of resource records in additional records
} __attribute__((packed));

struct dns_question {
    uint16 qtype;
    uint16 qclass;
} __attribute__((packed));

#define ARECORD (0x0001)
#define QCLASS (0x0001)

struct dns_data {
    uint16 type;
    uint16 class;
    uint32 ttl;
    uint16 len;
} __attribute__((packed));

extern void pci_init(void);

#endif // NET_H
